﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Design.Kernel.CAD;
using System.IO;
using DesignKernel.Exceptions;
using DesignKernel.CAD;
using System.Globalization;
using System.Drawing;

namespace Design.Kernel.IO
{
    public class StlLoader : FileLoader
    {

        const uint STL_RECORD_SIZE = 50;
        const int MAX_MODEL_NAME_LENGTH = 80;
        const int STL_HEADER_SIZE = MAX_MODEL_NAME_LENGTH + 4;
        readonly string SOLID = "solid";
        readonly string OUTER_LOOP = "outer loop";
        readonly string FACET_NORMAL = "facet normal";
        readonly string END_SOLID = "endsolid";
        readonly string END_LOOP = "endloop";
        readonly string END_FACET = "endfacet";
        Color DEFAULT_COLOR = Color.Green;

        /// <summary>
        /// Loads the triangle collection from the specified class
        /// </summary>
        /// <param name="fileName">File complete path</param>
        /// <returns>The model constructed from file</returns>
        public override TModel LoadFile(string fileName)
        {

            TModel model = new TModel();

            using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                uint triangleCount = 0;

                if (stream.Length < STL_HEADER_SIZE)
                    throw (new InvalidStlException("Invalid STL file"));

                byte[] nameBytes = new byte[MAX_MODEL_NAME_LENGTH];
                byte[] countBytes = new byte[4];
                byte[] version = new byte[1];
                byte[] encrypted = new byte[1];

                stream.Read(nameBytes, 0, 5);
                if (String.Compare(Encoding.UTF8.GetString(nameBytes, 0, 5), SOLID) == 0)
                {
                    // STL file begins with "solid". This is a hint that the STL file is in Ascii.
                    StreamReader st = new StreamReader(stream);
                    string name = st.ReadLine();
                    string firstLine = st.ReadLine();
                    if (firstLine.Contains(FACET_NORMAL))
                    {
                        // now we know for sure it's ascii!!!
                        model.Name = name.Trim(null);
                        ReadDataFromAsciiStl(st, model, true);
                        return model;
                    }

                    // ben benieuwd of het nog steeds ascii is....
                    // zet de file pointer even terug om verder te gaan.
                    stream.Seek(5, SeekOrigin.Begin);
                }

                stream.Read(nameBytes, 5, MAX_MODEL_NAME_LENGTH - 5);
                model.Name = (Encoding.UTF8.GetString(nameBytes, 0, nameBytes.Length)).TrimEnd(null);

                stream.Read(countBytes, 0, 4);
                try
                {
                    //Bugspray (RV) : Replaced 2^x with values, ^ is the exclusive or operator.
                    //Bugspray (YH) : Replaced hard coded number with Math.Pow()
                    triangleCount = (uint)countBytes[0]
                        + (uint)countBytes[1] * (uint)Math.Pow(2, 8) //256 /*2^8*/ 
                        + (uint)countBytes[2] * (uint)Math.Pow(2, 16) //65536 /*2^16*/ 
                        + (uint)countBytes[3] * (uint)Math.Pow(2, 24); //16777216 /*2^24*/;
                }
                catch
                {
                    throw new InvalidStlException("Invalid STL file");
                }

                // The file size should be as large as the header + (the number of triangles * sizeof triangle)
                if (stream.Length != STL_HEADER_SIZE + (triangleCount * STL_RECORD_SIZE))
                    throw (new InvalidStlException("Invalid STL file"));

               ReadData(stream, model, triangleCount);

            }


            return model;
        }

        #region binary readers
        void ReadData(FileStream stream, TModel model, uint triangleCount)
        {
            double factor;

            using (BinaryReader binaryReader = new BinaryReader(stream, new ASCIIEncoding()))
            {
                factor = 100.0 / triangleCount;
               
                byte[] dummy = new byte[12];

                for (uint i = 0; i < triangleCount; ++i)
                {
                    TTriangle triangle = TTriangle.Empty;

                    //Don't need first 12 bytes, the normal of the triangle
                    stream.Read(dummy, 0, 12);

                    triangle.Vertex0.x = binaryReader.ReadSingle();
                    triangle.Vertex0.y = binaryReader.ReadSingle();
                    triangle.Vertex0.z = binaryReader.ReadSingle();
                    triangle.Vertex1.x = binaryReader.ReadSingle();
                    triangle.Vertex1.y = binaryReader.ReadSingle();
                    triangle.Vertex1.z = binaryReader.ReadSingle();
                    triangle.Vertex2.x = binaryReader.ReadSingle();
                    triangle.Vertex2.y = binaryReader.ReadSingle();
                    triangle.Vertex2.z = binaryReader.ReadSingle();

                    UInt16 stlColor = binaryReader.ReadUInt16();
                    triangle.TriangleColor = GetReadTriangleColor(stlColor);

                    model.AddTriangle(triangle);

                }
            }

        }


        private Color GetReadTriangleColor(ushort stlColor)
        {
            float fBlue, fGreen, fRed;
            byte ubRed, ubGreen, ubBlue;
            Color result;


            fBlue = stlColor & 0x1F;
            fGreen = (stlColor >> 5) & 0x1F;
            fRed = (stlColor >> 10) & 0x1F;
            ubRed = (byte)(fRed / 32 * 256);
            ubGreen = (byte)(fGreen / 32 * 256);
            ubBlue = (byte)(fBlue / 32 * 256);

            result = Color.FromArgb(ubRed, ubGreen, ubBlue);

            if (ubRed != 0 || ubGreen != 0 || ubBlue != 0)
                return result;

            return DEFAULT_COLOR;


        }
        #endregion

        #region Ascii readers
        private TModel ReadDataFromAsciiStl(StreamReader reader, TModel model, bool skipFirstLines)
        {
            string buffer = null;
            int lineNumber = (skipFirstLines ? 2 : 1);

            try
            {
                while (true) // Loop through 
                {
                    TTriangle triangle = TTriangle.Empty;
                    if (!skipFirstLines)
                    {
                        buffer = reader.ReadLine(); ++lineNumber;
                        if (buffer == null) throw new IOException("Unexpected end of file");
                        if (!buffer.Contains(FACET_NORMAL))
                        {
                            if (buffer.StartsWith(END_SOLID))
                                break;
                            else
                                throw new ArgumentException("Unknwon keyword");
                        }
                    }
                    else
                        skipFirstLines = false;

                    ReadDataAsciiKeywordHelper(reader, OUTER_LOOP, ref lineNumber); // Skip "outer loop"

                    triangle.Vertex0 = this.ReadDataAsciiVertexHelper(reader, ref lineNumber);
                    triangle.Vertex1 = this.ReadDataAsciiVertexHelper(reader, ref lineNumber);
                    triangle.Vertex2 = this.ReadDataAsciiVertexHelper(reader, ref lineNumber);

                    ReadDataAsciiKeywordHelper(reader, END_LOOP, ref lineNumber);// Skip "endloop"
                    ReadDataAsciiKeywordHelper(reader, END_FACET, ref lineNumber);// Skip "endfacet"

                    model.AddTriangle(triangle);
                }
            }
            catch (Exception exception)
            {
                throw new InvalidStlException("Error reading ASCII STL file at line (" + lineNumber + ") : " + exception.Message);
            }

            return model;
        }

        private TVertex ReadDataAsciiVertexHelper(StreamReader reader, ref int LineNumber)
        {
            string buffer;
            TVertex tvertex;

            if (null == (buffer = reader.ReadLine())) throw new Exception("Unexpected end of file");
            LineNumber++;
            string[] elems = buffer.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
            if (elems.Length != 4) throw new ArgumentException("Syntax error in vertex specification");
            tvertex.x = float.Parse(elems[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo);
            tvertex.y = float.Parse(elems[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo);
            tvertex.z = float.Parse(elems[3], NumberStyles.Float, NumberFormatInfo.InvariantInfo);
            return tvertex;
        }

        private void ReadDataAsciiKeywordHelper(StreamReader reader, string Keyword, ref int LineNumber)
        {
            string buffer;
            if (null == (buffer = reader.ReadLine())) throw new Exception("Unexpected end of file");
            ++LineNumber;
            if (!buffer.Contains(Keyword)) throw new Exception("Expected '" + Keyword + "'");
        }
        #endregion
    }
}
